// 版权归Go作者所有。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

package modfetch

import (
	"archive/zip"
	"bytes"
	"context"
	"crypto/sha256"
	"encoding/base64"
	"errors"
	"fmt"
	"io"
	"io/fs"
	"os"
	"path/filepath"
	"sort"
	"strings"
	"sync"

	"cmd/go/internal/base"
	"cmd/go/internal/cfg"
	"cmd/go/internal/fsys"
	"cmd/go/internal/lockedfile"
	"cmd/go/internal/par"
	"cmd/go/internal/robustio"
	"cmd/go/internal/trace"

	"golang.org/x/mod/module"
	"golang.org/x/mod/sumdb/dirhash"
	modzip "golang.org/x/mod/zip"
)

var downloadCache par.Cache

// 下载将特定模块版本下载到
// 本地下载缓存并返回目录的名称
// 对应于模块文件树的根。
func Download(ctx context.Context, mod module.Version) (dir string, err error) {
	if err := checkCacheDir(); err != nil {
		base.Fatalf("go: %v", err)
	}

	// 这里的PAR缓存避免了重复的工作。
	type cached struct {
		dir string
		err error
	}
	c := downloadCache.Do(mod, func() interface{} {
		dir, err := download(ctx, mod)
		if err != nil {
			return cached{"", err}
		}
		checkMod(mod)
		return cached{dir, nil}
	}).(cached)
	return c.dir, c.err
}

func download(ctx context.Context, mod module.Version) (dir string, err error) {
	ctx, span := trace.StartSpan(ctx, "modfetch.download "+mod.String())
	defer span.Done()

	dir, err = DownloadDir(mod)
	if err == nil {
		// 目录已完全解压缩（不存在.partial文件）。
		return dir, nil
	} else if dir == "" || !errors.Is(err, fs.ErrNotExist) {
		return "", err
	}

	// 为了避免缓存中的无关文件造成混乱，
	// DownloadZip使用与下载相同的锁文件。
	// 在锁定文件之前调用DownloadZip。
	zipfile, err := DownloadZip(ctx, mod)
	if err != nil {
		return "", err
	}

	unlock, err := lockVersion(mod)
	if err != nil {
		return "", err
	}
	defer unlock()

	ctx, span = trace.StartSpan(ctx, "unzip "+zipfile)
	defer span.Done()

	// 检查在等待锁时目录是否已填充。
	_, dirErr := DownloadDir(mod)
	if dirErr == nil {
		return dir, nil
	}
	_, dirExists := dirErr.(*DownloadDirPartialError)

	// 清理由旧版本创建的所有剩余临时目录
	// （1.16之前），以及部分提取的目录（由
	// DownloadDirPartialError，通常是由于.partial文件）。这只是
	// 这样做是安全的，因为锁文件确保它们的写入程序不再
	// 忙碌的
	parentDir := filepath.Dir(dir)
	tmpPrefix := filepath.Base(dir) + ".tmp-"
	if old, err := filepath.Glob(filepath.Join(parentDir, tmpPrefix+"*")); err == nil {
		for _, path := range old {
			RemoveAll(path) // 尽力
		}
	}
	if dirExists {
		if err := RemoveAll(dir); err != nil {
			return "", err
		}
	}

	partialPath, err := CachePath(mod, "partial")
	if err != nil {
		return "", err
	}

	// 在其最终位置提取模块zip目录。
	// None
	// 为了防止其他进程在崩溃时读取目录，
	// 在提取目录之前创建.partial文件，然后删除
	// 之后是.partial文件（在保持锁的同时）。
	// None
	// 在Go 1.16之前，我们使用一个随机名称提取到一个临时目录
	// 然后使用os.Rename将其重命名为适当的位置。在Windows上，此操作失败
	// 当另一个进程（通常是防病毒扫描程序）被拒绝时发生错误\u访问\u
	// 在临时目录中打开文件。
	// None
	// 转到1.14.2及更高版本。部分文件。旧版本可能会使用
	// 部分提取的目录。'go mod verify'可以检测到这一点，
	// “去清理-modcache”可以修复它。
	if err := os.MkdirAll(parentDir, 0777); err != nil {
		return "", err
	}
	if err := os.WriteFile(partialPath, nil, 0666); err != nil {
		return "", err
	}
	if err := modzip.Unzip(dir, mod, zipfile); err != nil {
		fmt.Fprintf(os.Stderr, "-> %s\n", err)
		if rmErr := RemoveAll(dir); rmErr == nil {
			os.Remove(partialPath)
		}
		return "", err
	}
	if err := os.Remove(partialPath); err != nil {
		return "", err
	}

	if !cfg.ModCacheRW {
		makeDirsReadOnly(dir)
	}
	return dir, nil
}

var downloadZipCache par.Cache

// DownloadZip将特定模块版本下载到
// 本地zip缓存并返回zip文件的名称。
func DownloadZip(ctx context.Context, mod module.Version) (zipfile string, err error) {
	// 这里的PAR缓存避免了重复的工作。
	type cached struct {
		zipfile string
		err     error
	}
	c := downloadZipCache.Do(mod, func() interface{} {
		zipfile, err := CachePath(mod, "zip")
		if err != nil {
			return cached{"", err}
		}
		ziphashfile := zipfile + "hash"

		// 如果zip和ziphash文件存在，则返回而不锁定。
		if _, err := os.Stat(zipfile); err == nil {
			if _, err := os.Stat(ziphashfile); err == nil {
				return cached{zipfile, nil}
			}
		}

		// zip或ziphash文件不存在。获取锁并创建它们。
		if cfg.CmdName != "mod download" {
			fmt.Fprintf(os.Stderr, "go: downloading %s %s\n", mod.Path, mod.Version)
		}
		unlock, err := lockVersion(mod)
		if err != nil {
			return cached{"", err}
		}
		defer unlock()

		if err := downloadZip(ctx, mod, zipfile); err != nil {
			return cached{"", err}
		}
		return cached{zipfile, nil}
	}).(cached)
	return c.zipfile, c.err
}

func downloadZip(ctx context.Context, mod module.Version, zipfile string) (err error) {
	ctx, span := trace.StartSpan(ctx, "modfetch.downloadZip "+zipfile)
	defer span.Done()

	// 再次检查zipfile是否在等待时未创建
	// 下载拉链中的锁。
	ziphashfile := zipfile + "hash"
	var zipExists, ziphashExists bool
	if _, err := os.Stat(zipfile); err == nil {
		zipExists = true
	}
	if _, err := os.Stat(ziphashfile); err == nil {
		ziphashExists = true
	}
	if zipExists && ziphashExists {
		return nil
	}

	// 创建父目录。
	if err := os.MkdirAll(filepath.Dir(zipfile), 0777); err != nil {
		return err
	}

	// 清除以前运行的所有剩余临时文件。
	// 这样做是安全的，因为锁文件确保
	// 作家不再活跃。
	tmpPattern := filepath.Base(zipfile) + "*.tmp"
	if old, err := filepath.Glob(filepath.Join(filepath.Dir(zipfile), tmpPattern)); err == nil {
		for _, path := range old {
			os.Remove(path) // 尽力
		}
	}

	// 如果zip文件存在，则ziphash文件必须已被删除
	// 或者在文件系统崩溃后丢失。在不下载的情况下重新散列zip。
	if zipExists {
		return hashZip(mod, zipfile, ziphashfile)
	}

	// 从这里到下面的os.Rename调用在功能上几乎等同于
	// renameio.WriteToFile，有一个关键区别：我们要验证
	// 在提交文件之前（通过散列）文件的内容。因为文件
	// 如果是zip压缩的，我们需要一个实际的文件，或者至少是一个io.ReaderAt文件
	// 验证它：我们不能在编写流时对其进行tee。
	f, err := os.CreateTemp(filepath.Dir(zipfile), tmpPattern)
	if err != nil {
		return err
	}
	defer func() {
		if err != nil {
			f.Close()
			os.Remove(f.Name())
		}
	}()

	var unrecoverableErr error
	err = TryProxies(func(proxy string) error {
		if unrecoverableErr != nil {
			return unrecoverableErr
		}
		repo := Lookup(proxy, mod.Path)
		err := repo.Zip(f, mod.Version)
		if err != nil {
			// Zip可能在失败之前已部分写入f。
			// （可能是服务器在发送文件时崩溃了？）
			// 由于我们在某些情况下允许对错误进行回退，因此需要修复
			// 下次尝试时，文件将再次为空。
			if _, err := f.Seek(0, io.SeekStart); err != nil {
				unrecoverableErr = err
				return err
			}
			if err := f.Truncate(0); err != nil {
				unrecoverableErr = err
				return err
			}
		}
		return err
	})
	if err != nil {
		return err
	}

	// 仔细检查zip文件中的路径是否格式正确。
	// None
	// TODO（bcmills）：解压函数中也有类似的检查。我们能消除一个吗？
	fi, err := f.Stat()
	if err != nil {
		return err
	}
	z, err := zip.NewReader(f, fi.Size())
	if err != nil {
		return err
	}
	prefix := mod.Path + "@" + mod.Version + "/"
	for _, f := range z.File {
		if !strings.HasPrefix(f.Name, prefix) {
			return fmt.Errorf("zip for %s has unexpected file %s", prefix[:len(prefix)-1], f.Name)
		}
	}

	if err := f.Close(); err != nil {
		return err
	}

	// 散列zip文件并在重命名到最终位置之前检查总和。
	if err := hashZip(mod, f.Name(), ziphashfile); err != nil {
		return err
	}
	if err := os.Rename(f.Name(), zipfile); err != nil {
		return err
	}

	// TODO（bcmills）：我们是否应该将.zip和.ziphash文件设置为只读以防止篡改？

	return nil
}

// hashZip读取在f中打开的zip文件，然后将哈希写入ziphashfile，
// 覆盖该文件（如果存在）。
// None
// 如果哈希与go.sum（或sumdb，如果启用）不匹配，hashZip将返回
// 出现错误，无法写入ziphashfile。
func hashZip(mod module.Version, zipfile, ziphashfile string) error {
	hash, err := dirhash.HashZip(zipfile, dirhash.DefaultHash)
	if err != nil {
		return err
	}
	if err := checkModSum(mod, hash); err != nil {
		return err
	}
	hf, err := lockedfile.Create(ziphashfile)
	if err != nil {
		return err
	}
	if err := hf.Truncate(int64(len(hash))); err != nil {
		return err
	}
	if _, err := hf.WriteAt([]byte(hash), 0); err != nil {
		return err
	}
	if err := hf.Close(); err != nil {
		return err
	}

	return nil
}

// MakeDirAdonly尽最大努力删除目录的写入权限
// 及其及物性内容。
func makeDirsReadOnly(dir string) {
	type pathMode struct {
		path string
		mode fs.FileMode
	}
	var dirs []pathMode // 按词序
	filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
		if err == nil && d.IsDir() {
			info, err := d.Info()
			if err == nil && info.Mode()&0222 != 0 {
				dirs = append(dirs, pathMode{path, info.Mode()})
			}
		}
		return nil
	})

	// 将列表向后运行到chmod子级，然后再运行到父级。
	for i := len(dirs) - 1; i >= 0; i-- {
		os.Chmod(dirs[i].path, dirs[i].mode&^0222)
	}
}

// RemoveAll删除通过下载或解压缩写入的目录，首先应用
// 执行此操作所需的任何权限更改。
func RemoveAll(dir string) error {
	// 模块缓存有0555个目录；使其可写以删除内容。
	filepath.WalkDir(dir, func(path string, info fs.DirEntry, err error) error {
		if err != nil {
			return nil // 忽略文件系统中的错误
		}
		if info.IsDir() {
			os.Chmod(path, 0777)
		}
		return nil
	})
	return robustio.RemoveAll(dir)
}

var GoSumFile string // 要走的路；由包modload设置

type modSum struct {
	mod module.Version
	sum string
}

var goSum struct {
	mu        sync.Mutex
	m         map[module.Version][]string // go.sum文件的内容
	status    map[modSum]modSumStatus     // m中和的状态
	overwrite bool                        // 如果为true，则覆盖go.sum而不合并其内容
	enabled   bool                        // 是否使用go.sum
}

type modSumStatus struct {
	used, dirty bool
}

// initGoSum初始化go.sum数据。
// 它返回的布尔值报告
// 现在已启用go.sum的使用。
// 必须保持goSum锁。
func initGoSum() (bool, error) {
	if GoSumFile == "" {
		return false, nil
	}
	if goSum.m != nil {
		return true, nil
	}

	goSum.m = make(map[module.Version][]string)
	goSum.status = make(map[modSum]modSumStatus)
	var (
		data []byte
		err  error
	)
	if actualSumFile, ok := fsys.OverlayPath(GoSumFile); ok {
		// 如果go.sum是覆盖的一部分，则不要锁定它。
		// 在计划9中，锁定需要chmod，我们不想修改任何文件
		// 在覆盖层中。见#44700。
		data, err = os.ReadFile(actualSumFile)
	} else {
		data, err = lockedfile.Read(GoSumFile)
	}
	if err != nil && !os.IsNotExist(err) {
		return false, err
	}
	goSum.enabled = true
	readGoSum(goSum.m, GoSumFile, data)

	return true, nil
}

// emptyGoModHash是包含0长度go.mod的1文件树的哈希。
// 一个错误导致我们将这些写入非模块的go.sum文件。
// 我们检测并移除它们。
const emptyGoModHash = "h1:G7mAYYxgmS0lVkHyy2hEOLQCFB0DlQFTMLWggykrydY="

// readGoSum解析数据，即文件的内容，
// 并将其添加到goSum.m。必须保持goSum锁。
func readGoSum(dst map[module.Version][]string, file string, data []byte) error {
	lineno := 0
	for len(data) > 0 {
		var line []byte
		lineno++
		i := bytes.IndexByte(data, '\n')
		if i < 0 {
			line, data = data, nil
		} else {
			line, data = data[:i], data[i+1:]
		}
		f := strings.Fields(string(line))
		if len(f) == 0 {
			// 空白行；跳过它
			continue
		}
		if len(f) != 3 {
			return fmt.Errorf("malformed go.sum:\n%s:%d: wrong number of fields %v", file, lineno, len(f))
		}
		if f[2] == emptyGoModHash {
			// 老虫子；放下它。
			continue
		}
		mod := module.Version{Path: f[0], Version: f[1]}
		dst[mod] = append(dst[mod], f[2])
	}
	return nil
}

// 如果go.sum文件包含mod的条目，则HaveSum返回true。
// 条目的哈希必须使用已知的哈希算法生成。
// mod.Version可能有一个“/go.mod”后缀来区分
// .mod和.zip文件。
func HaveSum(mod module.Version) bool {
	goSum.mu.Lock()
	defer goSum.mu.Unlock()
	inited, err := initGoSum()
	if err != nil || !inited {
		return false
	}
	for _, h := range goSum.m[mod] {
		if !strings.HasPrefix(h, "h1:") {
			continue
		}
		if !goSum.status[modSum{mod, h}].dirty {
			return true
		}
	}
	return false
}

// checkMod检查给定模块的校验和。
func checkMod(mod module.Version) {
	// 在获取go.sum锁之前执行文件I/O。
	ziphash, err := CachePath(mod, "ziphash")
	if err != nil {
		base.Fatalf("verifying %v", module.VersionError(mod, err))
	}
	data, err := lockedfile.Read(ziphash)
	if err != nil {
		base.Fatalf("verifying %v", module.VersionError(mod, err))
	}
	data = bytes.TrimSpace(data)
	if !isValidSum(data) {
		// 从zip文件重新创建ziphash文件，并使用该文件检查mod sum。
		zip, err := CachePath(mod, "zip")
		if err != nil {
			base.Fatalf("verifying %v", module.VersionError(mod, err))
		}
		err = hashZip(mod, zip, ziphash)
		if err != nil {
			base.Fatalf("verifying %v", module.VersionError(mod, err))
		}
		return
	}
	h := string(data)
	if !strings.HasPrefix(h, "h1:") {
		base.Fatalf("verifying %v", module.VersionError(mod, fmt.Errorf("unexpected ziphash: %q", h)))
	}

	if err := checkModSum(mod, h); err != nil {
		base.Fatalf("%s", err)
	}
}

// goModSum返回go.mod内容的校验和。
func goModSum(data []byte) (string, error) {
	return dirhash.Hash1([]string{"go.mod"}, func(string) (io.ReadCloser, error) {
		return io.NopCloser(bytes.NewReader(data)), nil
	})
}

// checkGoMod检查给定模块的go.mod校验和；
// 数据是go.mod内容。
func checkGoMod(path, version string, data []byte) error {
	h, err := goModSum(data)
	if err != nil {
		return &module.ModuleError{Path: path, Version: version, Err: fmt.Errorf("verifying go.mod: %v", err)}
	}

	return checkModSum(module.Version{Path: path, Version: version + "/go.mod"}, h)
}

// checkModSum检查记录的mod校验和是否为h。
// None
// mod.Version可能有额外的后缀“/go.mod”来请求校验和
// 仅适用于模块的go.mod文件。
func checkModSum(mod module.Version, h string) error {
	// 我们在操作goSum时锁定它，
	// 但是我们在调用checkSumDB时会释放锁，
	// 因此，对checkModHash的并行调用可以执行并行调用
	// 检查sumdb。

	// 检查go.sum中是否已列出mod+h。如果是这样，我们就完了。
	goSum.mu.Lock()
	inited, err := initGoSum()
	if err != nil {
		goSum.mu.Unlock()
		return err
	}
	done := inited && haveModSumLocked(mod, h)
	if inited {
		st := goSum.status[modSum{mod, h}]
		st.used = true
		goSum.status[modSum{mod, h}] = st
	}
	goSum.mu.Unlock()

	if done {
		return nil
	}

	// 未列出，因此我们要添加它们。
	// 如果合适，请查阅校验和数据库。
	if useSumDB(mod) {
		// 如果检测到不匹配，则调用base.Fatalf。
		if err := checkSumDB(mod, h); err != nil {
			return err
		}
	}

	// 将mod+h添加到go.sum，如果它尚未出现。
	if inited {
		goSum.mu.Lock()
		addModSumLocked(mod, h)
		st := goSum.status[modSum{mod, h}]
		st.dirty = true
		goSum.status[modSum{mod, h}] = st
		goSum.mu.Unlock()
	}
	return nil
}

// haveModSumLocked报告是否已在go.sum中列出mod，h对。
// 如果它找到一个冲突对，则调用base.Fatalf。
// 必须锁定goSum.mu。
func haveModSumLocked(mod module.Version, h string) bool {
	for _, vh := range goSum.m[mod] {
		if h == vh {
			return true
		}
		if strings.HasPrefix(vh, "h1:") {
			base.Fatalf("verifying %s@%s: checksum mismatch\n\tdownloaded: %v\n\tgo.sum:     %v"+goSumMismatch, mod.Path, mod.Version, h, vh)
		}
	}
	return false
}

// addModSumlock将对mod，h添加到go.sum。
// 必须锁定goSum.mu。
func addModSumLocked(mod module.Version, h string) {
	if haveModSumLocked(mod, h) {
		return
	}
	if len(goSum.m[mod]) > 0 {
		fmt.Fprintf(os.Stderr, "warning: verifying %s@%s: unknown hashes in go.sum: %v; adding %v"+hashVersionMismatch, mod.Path, mod.Version, strings.Join(goSum.m[mod], ", "), h)
	}
	goSum.m[mod] = append(goSum.m[mod], h)
}

// checkSumDB根据Go校验和数据库检查mod，h对。
// 如果要拒绝哈希，它将调用base.Fatalf。
func checkSumDB(mod module.Version, h string) error {
	modWithoutSuffix := mod
	noun := "module"
	if strings.HasSuffix(mod.Version, "/go.mod") {
		noun = "go.mod"
		modWithoutSuffix.Version = strings.TrimSuffix(mod.Version, "/go.mod")
	}

	db, lines, err := lookupSumDB(mod)
	if err != nil {
		return module.VersionError(modWithoutSuffix, fmt.Errorf("verifying %s: %v", noun, err))
	}

	have := mod.Path + " " + mod.Version + " " + h
	prefix := mod.Path + " " + mod.Version + " h1:"
	for _, line := range lines {
		if line == have {
			return nil
		}
		if strings.HasPrefix(line, prefix) {
			return module.VersionError(modWithoutSuffix, fmt.Errorf("verifying %s: checksum mismatch\n\tdownloaded: %v\n\t%s: %v"+sumdbMismatch, noun, h, db, line[len(prefix)-len("h1:"):]))
		}
	}
	return nil
}

// Sum返回给定模块的下载副本的校验和，
// 如果存在于下载缓存中。
func Sum(mod module.Version) string {
	if cfg.GOMODCACHE == "" {
		// 不要使用当前目录。
		return ""
	}

	ziphash, err := CachePath(mod, "ziphash")
	if err != nil {
		return ""
	}
	data, err := lockedfile.Read(ziphash)
	if err != nil {
		return ""
	}
	data = bytes.TrimSpace(data)
	if !isValidSum(data) {
		return ""
	}
	return string(data)
}

// 如果数据是zip哈希文件的有效内容，则isValidSum返回true。
// 某些关键文件首先通过截断写入磁盘
// 然后写入实际字节，以便在写入失败时
// 损坏的文件应至少包含一个空文件
// 由truncate操作写入的字节数。
func isValidSum(data []byte) bool {
	if bytes.IndexByte(data, '\000') >= 0 {
		return false
	}

	if len(data) != len("h1:")+base64.StdEncoding.EncodedLen(sha256.Size) {
		return false
	}

	return true
}

// WriteGoSum在需要更新时写入go.sum文件。
// None
// keep用于检查新添加的金额是否应保存在go.sum中。
// 它应该有模块内容和go.mod和的条目
// （版本以“/go.mod”结尾）。现有款项将予以保留，除非
// 已用TrimGoSum标记为删除。
func WriteGoSum(keep map[module.Version]bool) {
	goSum.mu.Lock()
	defer goSum.mu.Unlock()

	// 如果我们还没有读取go.sum文件，就不用费心写了。
	if !goSum.enabled {
		return
	}

	// 检查是否需要添加keep[m]为真的和或删除
	// 用TrimGoSum标记的未使用金额。如果没有变化，
	// 不用打开go.sum就可以返回。
	dirty := false
Outer:
	for m, hs := range goSum.m {
		for _, h := range hs {
			st := goSum.status[modSum{m, h}]
			if st.dirty && (!st.used || keep[m]) {
				dirty = true
				break Outer
			}
		}
	}
	if !dirty {
		return
	}
	if cfg.BuildMod == "readonly" {
		base.Fatalf("go: updates to go.sum needed, disabled by -mod=readonly")
	}
	if _, ok := fsys.OverlayPath(GoSumFile); ok {
		base.Fatalf("go: updates to go.sum needed, but go.sum is part of the overlay specified with -overlay")
	}

	// 尽最大努力获取侧锁，仅排除
	// “go”命令的早期版本无法同时进行编辑。
	if unlock, err := SideLock(); err == nil {
		defer unlock()
	}

	err := lockedfile.Transform(GoSumFile, func(data []byte) ([]byte, error) {
		if !goSum.overwrite {
			// 同时合并其他流程增加的任何金额。
			// 仅添加我们实际检查的总和：用户可能已编辑或
			// 截断文件以删除错误的哈希，我们不应该恢复
			// 他们没有充分的理由。
			goSum.m = make(map[module.Version][]string, len(goSum.m))
			readGoSum(goSum.m, GoSumFile, data)
			for ms, st := range goSum.status {
				if st.used {
					addModSumLocked(ms.mod, ms.sum)
				}
			}
		}

		var mods []module.Version
		for m := range goSum.m {
			mods = append(mods, m)
		}
		module.Sort(mods)

		var buf bytes.Buffer
		for _, m := range mods {
			list := goSum.m[m]
			sort.Strings(list)
			for _, h := range list {
				st := goSum.status[modSum{m, h}]
				if !st.dirty || (st.used && keep[m]) {
					fmt.Fprintf(&buf, "%s %s %s\n", m.Path, m.Version, h)
				}
			}
		}
		return buf.Bytes(), nil
	})

	if err != nil {
		base.Fatalf("go: updating go.sum: %v", err)
	}

	goSum.status = make(map[modSum]modSumStatus)
	goSum.overwrite = false
}

// TrimGoSum trims go.sum仅包含再现所需的模块
// 建立。
// None
// keep用于检查是否应在go.mod中保留一笔金额。它应该
// 具有模块内容和go.mod和的条目（版本结束
// 使用“/go.mod”）。
func TrimGoSum(keep map[module.Version]bool) {
	goSum.mu.Lock()
	defer goSum.mu.Unlock()
	inited, err := initGoSum()
	if err != nil {
		base.Fatalf("%s", err)
	}
	if !inited {
		return
	}

	for m, hs := range goSum.m {
		if !keep[m] {
			for _, h := range hs {
				goSum.status[modSum{m, h}] = modSumStatus{used: false, dirty: true}
			}
			goSum.overwrite = true
		}
	}
}

const goSumMismatch = `

SECURITY ERROR
This download does NOT match an earlier download recorded in go.sum.
The bits may have been replaced on the origin server, or an attacker may
have intercepted the download attempt.

For more information, see 'go help module-auth'.
`

const sumdbMismatch = `

SECURITY ERROR
This download does NOT match the one reported by the checksum server.
The bits may have been replaced on the origin server, or an attacker may
have intercepted the download attempt.

For more information, see 'go help module-auth'.
`

const hashVersionMismatch = `

SECURITY WARNING
This download is listed in go.sum, but using an unknown hash algorithm.
The download cannot be verified.

For more information, see 'go help module-auth'.

`

var HelpModuleAuth = &base.Command{
	UsageLine: "module-auth",
	Short:     "module authentication using go.sum",
	Long: `
When the go command downloads a module zip file or go.mod file into the
module cache, it computes a cryptographic hash and compares it with a known
value to verify the file hasn't changed since it was first downloaded. Known
hashes are stored in a file in the module root directory named go.sum. Hashes
may also be downloaded from the checksum database depending on the values of
GOSUMDB, GOPRIVATE, and GONOSUMDB.

For details, see https:// golang.org/ref/mod#身份验证。
`,
}

var HelpPrivate = &base.Command{
	UsageLine: "private",
	Short:     "configuration for downloading non-public code",
	Long: `
The go command defaults to downloading modules from the public Go module
mirror at proxy.golang.org. It also defaults to validating downloaded modules,
regardless of source, against the public Go checksum database at sum.golang.org.
These defaults work well for publicly available source code.

The GOPRIVATE environment variable controls which modules the go command
considers to be private (not available publicly) and should therefore not use
the proxy or checksum database. The variable is a comma-separated list of
glob patterns (in the syntax of Go's path.Match) of module path prefixes.
For example,

	GOPRIVATE=*.corp.example.com,rsc.io/private

causes the go command to treat as private any module with a path prefix
matching either pattern, including git.corp.example.com/xyzzy, rsc.io/private,
and rsc.io/private/quux.

For fine-grained control over module download and validation, the GONOPROXY
and GONOSUMDB environment variables accept the same kind of glob list
and override GOPRIVATE for the specific decision of whether to use the proxy
and checksum database, respectively.

For example, if a company ran a module proxy serving private modules,
users would configure go using:

	GOPRIVATE=*.corp.example.com
	GOPROXY=proxy.example.com
	GONOPROXY=none

The GOPRIVATE variable is also used to define the "public" and "private"
patterns for the GOVCS variable; see 'go help vcs'. For that usage,
GOPRIVATE applies even in GOPATH mode. In that case, it matches import paths
instead of module paths.

The 'go env -w' command (see 'go help env') can be used to set these variables
for future go command invocations.

For more details, see https:// org/ref/mod#私有模块。
`,
}
